#include <windows.h>
#include <iostream>
#include <ctime>
#include <vector>
#include <fstream>
#include "gl\gl.h"
#include "gl\glu.h"
#include "gl\glut.h"
#include "gl\glaux.h"
#include "tgaLoader.h"
#include "player.h"
#include "imageLoader.h"
#include "models/md2model.h"
#include "constants.h"
#include "math.h"
#include "error.h"
#include "handyFunctions.h"
#include "gameParameters.h"
#include "objLoader.h"

//TODO: fix
#pragma comment(lib,"glaux.lib")
#pragma comment(lib,"glut32.lib")
#pragma comment(lib,"opengl32.lib")
#pragma comment(lib,"glu32.lib")

bool ObjLoader::importObj(t3DModel *model, char *fileName)
{
	char strMessage[255] = {0};

	printf("Loading model: %s...\n", fileName);
	
	if(!model || !fileName) return false;

	// Check to make sure we have a valid file pointer
	if(!filePointer)
	{
		Error::printError(MODEL_NOT_FOUND, fileName);
		return false;
	}

	// Now that we have a valid file and it's open, let's read in the info
	readObjFile(model);

	// Now that we have the file read in, let's compute the vertex normals for lighting
	computeNormals(model);

	// Here we go through all of the materials and check if we need to make a texture map.
	// We obviously know there is just one material but we should set this up for future use.
	for(int i = 0; i < model->numOfMaterials; i++)
	{
		// Check if the current material has a file name
		if(strlen(model->materials[i].strFile) > 0) {

			// Create a texture map from the material's file name
			createTexture(model->g_Texture, model->materials[i].strFile, i);
		}
		
		// Assign the material ID to the current material
		model->materials[i].texureId = i;
	}

	// Close the .obj file that we opened
	fclose(filePointer);

	return true;
}

//main loop for reading in the .obj file
void ObjLoader::readObjFile(t3DModel *model)
{
	char strLine[255]		= {0};
	char ch					= 0;

	while(!feof(filePointer))
	{
		float x = 0.0f, y = 0.0f, z = 0.0f;

		// Get the beginning character of the current line in the .obj file
		ch = fgetc(filePointer);

		switch(ch)
		{
		case 'o':
			readName();
			break;
		
		case 'v':						// Check if we just read in a 'v' (Could be a vertice/normal/textureCoord)
			
			// If we just read in a face line, then we have gone to another object,
			// so we need to save the last object's data before moving onto the next one.
			if(justReadAFace)
			{
				// Save the last object's info into our model structure
				fillInObjectInfo(model);
			}

			// Decipher this line to see if it's a vertex ("v"), normal ("vn"), or UV coordinate ("vt")
			readVertexInfo();
			break;

		case 'u':
			readMaterialInfo();
			break;
		case 'f':						// Check if we just read in a face header ('f')
			
			// If we get here we then we need to read in the face information.
			// The face line holds the vertex indices into our vertex array, or if
			// the object has texture coordinates then it holds those too.
			readFaceInfo();
			break;

		case '\n':

			// If we read in a newline character, we've encountered a blank line in
			// the .obj file.  We don't want to do the default case and skip another
			// line, so we just break and do nothing.
			break;

		default:

			// If we get here then we don't care about the line being read, so read past it.
			fgets(strLine, 100, filePointer);
			break;
		}
	}
	
	// Now that we are done reading in the file, we have need to save the last object read.
	fillInObjectInfo(model);
}

//This function reads in the vertex information ("v" vertex : "vt" UVCoord)
void ObjLoader::readName()
{
	char strLine[255]		= {0};
	char ch = 0;

	ch = fgetc(filePointer);

	if(ch == ' ')				// If we get a space it must have been a name of object ("o")
	{
		// Here we read in a name.
		fscanf_s(filePointer, "%s", &name);

		// Read the rest of the line so the file pointer returns to the next line.
		fgets(strLine, 100, filePointer);
	}
	else						// Otherwise it's probably something else than a name so we don't care
	{
		fgets(strLine, 100, filePointer);
	}
}

//This function reads in the texture name (it has to be the same as material name, and be a *.bmp file
void ObjLoader::readMaterialInfo()
{
	char strLine[255]		= {0};
	char ch = 0;
	// Read the next character in the file
	ch = fgetc(filePointer);

	if(ch == 's')			// If we get a 't' then it must be a texture coordinate ("vt")
	{
		fscanf_s(filePointer, "%s %s", t_name, t_path);
		if(!strcmp(t_name, "emtl"))
		{
			strcpy_s(t_name, t_path);
			strcat_s(t_path, ".bmp");
			// Read the rest of the line so the file pointer returns to the next line.
		}
		else
		{
			strcpy_s(t_name, "\0");
			strcat_s(t_path, "\0");
		}
	}
	else						// Otherwise we don't care
	{
		fgets(strLine, 100, filePointer);
	}
}

//This function reads in the vertex information ("v" vertex : "vt" UVCoord)
void ObjLoader::readVertexInfo()
{
	CVector3 newVertex		= {0};
	CVector2 newTexCoordinates	= {0};
	char strLine[255]		= {0};
	char ch = 0;

	// Read the next character in the file to see if it's a vertice/normal/UVCoord
	ch = fgetc(filePointer);

	if(ch == ' ')				// If we get a space it must have been a vertex ("v")
	{
		// Here we read in a vertice.  The format is "v x y z"
		fscanf_s(filePointer, "%g %g %g", &newVertex.x, &newVertex.y, &newVertex.z);

		// Read the rest of the line so the file pointer returns to the next line.
		fgets(strLine, 100, filePointer);

		// Add a new vertice to our list
		vertices.push_back(newVertex);
	}
	else if(ch == 't')			// If we get a 't' then it must be a texture coordinate ("vt")
	{
		// Here we read in a texture coordinate.  The format is "vt u v"
		fscanf_s(filePointer, "%g %g", &newTexCoordinates.x, &newTexCoordinates.y);

		// Read the rest of the line so the file pointer returns to the next line.
		fgets(strLine, 100, filePointer);

		// Add a new texture coordinate to our list
		textureCoordinates.push_back(newTexCoordinates);

		// Set the flag that tells us this object has texture coordinates.
		// Now we know that the face information will list the vertice AND UV index.
		// For example, ("f 1 3 2" verses "f 1/1 2/2 3/3")
		objectHasUV = true;
	}
	else						// Otherwise it's probably a normal so we don't care ("vn")
	{
		// We calculate our own normals at the end so we read past them.
		fgets(strLine, 100, filePointer);
	}
}


//This function reads in the face information ("f")
void ObjLoader::readFaceInfo()
{
	Face newFace			= {0};
	char strLine[255]		= {0};

	// Check if this object has texture coordinates before reading in the values
	if(objectHasUV)
	{
		fscanf_s(filePointer, "%d/%d %d/%d %d/%d", &newFace.vertIndex[0], &newFace.coordIndex[0],
													&newFace.vertIndex[1], &newFace.coordIndex[1],
													&newFace.vertIndex[2], &newFace.coordIndex[2]);	

	
			/*printf("%d/%d %d/%d %d/%d\n",	newFace.vertIndex[0], newFace.coordIndex[0],
											newFace.vertIndex[1], newFace.coordIndex[1],
											newFace.vertIndex[2], newFace.coordIndex[2]);*/
		
	}
	else										// The object does NOT have texture coordinates
	{
		// Here we read in just the object's vertex indices.
		// Here is the format: "f vertexIndex1 vertexIndex2 vertexIndex3"
		fscanf_s(filePointer, "%d %d %d",		&newFace.vertIndex[0],
												&newFace.vertIndex[1],
												&newFace.vertIndex[2]);
	}

	// Read the rest of the line so the file pointer returns to the next line.
	fgets(strLine, 100, filePointer);
				
	// Add the new face to our face list
	faces.push_back(newFace);

	justReadAFace = true;
}


//this function is called after an object is read in to fill in the model structure
void ObjLoader::fillInObjectInfo(t3DModel *model)
{
	t3DObject newObject = {0};
	int vertexOffset = 0;
	int textureOffset = 0;
	int i = 0;

	ifstream input;
	input.open(t_path, ifstream::binary);

	//file not found
	if (input.fail()) addMaterial(model, t_name, NULL, 255, 255, 255);
	else addMaterial(model, t_name, t_path);

	input.close();

	// If we get here then we just finished reading in an object
	// and need to increase the object count.
	model->numOfObjects++;

	// Add a new object to the list of objects in our model
	model->object.push_back(newObject);

	// Get a pointer to the current object so our code is easier to read
	t3DObject *object = &(model->object[model->numOfObjects - 1]);

	// Here we get the number of faces, vertices and texture coordinates
	object->name = name;

	object->numberOfFaces   = (int)faces.size();
	object->numberOfVertices   = (int)vertices.size();
	object->numberOfTexVertices = (int)textureCoordinates.size();

	// If we read in any faces for this object (required)
	if(object->numberOfFaces)
	{
		// Allocate enough memory to store all the faces in our object
		object->faces = new Face [object->numberOfFaces];
	}

	// If we read in any vertices for this object (required)
	if(object->numberOfVertices)
	{
		// Allocate enough memory to store all the vertices in our object
		object->vertices = new CVector3 [object->numberOfVertices];
	}	

	// If we read in any texture coordinates for this object (optional)
	if(object->numberOfTexVertices)
	{
		object->textureVertices = new CVector2 [object->numberOfTexVertices];
		object->hasTexture = true;
	}	

	// Creating an offset
	if(model->numOfObjects > 1)
	{
		for(int j = 0; j < model->numOfObjects-1; j++)
		{
			vertexOffset += model->object[j].numberOfVertices;
			textureOffset += model->object[j].numberOfTexVertices;
		}					
	}


	// Go through all of the faces in the object
	for(i = 0; i < object->numberOfFaces; i++)
	{
		// Copy the current face from the temporary list to our Model list
		object->faces[i] = faces[i];

		for(int j = 0; j < 3; j++)
		{
			// For each index, minus 1 to conform with zero based arrays.
			// We also need to add the vertex and texture offsets to subtract
			// the total amount necessary for this to work.  The first object
			// will have an offset of 0 for both since it starts at 1.
			object->faces[i].vertIndex[j]  -= 1 + vertexOffset;
			object->faces[i].coordIndex[j] -= 1 + textureOffset;
		}
	}

	// Go through all the vertices in the object
	for(i = 0; i < object->numberOfVertices; i++)
	{
		// Copy the current vertice from the temporary list to our Model list
		object->vertices[i] = vertices[i];
	}

	// Go through all of the texture coordinates in the object (if any)
	for(i = 0; i < object->numberOfTexVertices; i++)
	{
		// Copy the current UV coordinate from the temporary list to our Model list
		object->textureVertices[i] = textureCoordinates[i];
	}

	// Since .OBJ files don't have materials, we set the material ID to its index.
	// We assume grouping objects by materials.
	object->materialID = model->numOfObjects-1;

	// Now that we have all the information from out list's, we need to clear them
	// so we can be ready for the next object that we read in.
	vertices.clear();
	faces.clear();
	textureCoordinates.clear();

	// Reset these booleans to be prepared for the next object
	objectHasUV   = false;
	justReadAFace = false;
}



//This function adds a material to our model manually since .obj has no such info
void ObjLoader::addMaterial(t3DModel *model, char *strName, char *strFile, 
									int r,			  int g,		 int b)
{
	MaterialInfo newMaterial = {0};

	// Set the RGB value for this material [0 - RED		1 - GREEN	2 - BLUE]
	newMaterial.color[0] = r; newMaterial.color[1] = g; newMaterial.color[2] = b;

	// If we have a file name passed in, let's copy it to our material structure
	if(strFile)
	{
		strcpy_s(newMaterial.strFile, strFile);
	}

	// If we have a material name passed in, let's copy it to our material structure
	if(strName)
	{
		strcpy_s(newMaterial.strName, strName);
	}

	// Now we add this material to model's list
	model->materials.push_back(newMaterial);

	// Increase the material count
	model->numOfMaterials++;
}


//TODO: Use math functions from gameMath.cpp
//////////////////////////////	Math Functions  ////////////////////////////////*

// This computes the magnitude of a normal.   (magnitude = sqrt(x^2 + y^2 + z^2)
#define Mag(Normal) (sqrt(Normal.x*Normal.x + Normal.y*Normal.y + Normal.z*Normal.z))

// This calculates a vector between 2 points and returns the result
CVector3 Vector(CVector3 vPoint1, CVector3 vPoint2)
{
	CVector3 vector;							// The variable to hold the resultant vector

	vector.x = vPoint1.x - vPoint2.x;			// Subtract point1 and point2 x's
	vector.y = vPoint1.y - vPoint2.y;			// Subtract point1 and point2 y's
	vector.z = vPoint1.z - vPoint2.z;			// Subtract point1 and point2 z's

	return vector;								// Return the resultant vector
}

// This adds 2 vectors together and returns the result
CVector3 AddVector(CVector3 vVector1, CVector3 vVector2)
{
	CVector3 vResult;							// The variable to hold the resultant vector
	
	vResult.x = vVector2.x + vVector1.x;		// Add Vector1 and Vector2 x's
	vResult.y = vVector2.y + vVector1.y;		// Add Vector1 and Vector2 y's
	vResult.z = vVector2.z + vVector1.z;		// Add Vector1 and Vector2 z's

	return vResult;								// Return the resultant vector
}

// This divides a vector by a single number (scalar) and returns the result
CVector3 DivideVectorByScaler(CVector3 vVector1, float Scaler)
{
	CVector3 vResult;							// The variable to hold the resultant vector
	
	vResult.x = vVector1.x / Scaler;			// Divide Vector1's x value by the scaler
	vResult.y = vVector1.y / Scaler;			// Divide Vector1's y value by the scaler
	vResult.z = vVector1.z / Scaler;			// Divide Vector1's z value by the scaler

	return vResult;								// Return the resultant vector
}

// This returns the cross product between 2 vectors
CVector3 Cross(CVector3 vVector1, CVector3 vVector2)
{
	CVector3 vCross;								// The vector to hold the cross product
												// Get the X value
	vCross.x = ((vVector1.y * vVector2.z) - (vVector1.z * vVector2.y));
												// Get the Y value
	vCross.y = ((vVector1.z * vVector2.x) - (vVector1.x * vVector2.z));
												// Get the Z value
	vCross.z = ((vVector1.x * vVector2.y) - (vVector1.y * vVector2.x));

	return vCross;								// Return the cross product
}

// This returns the normal of a vector
CVector3 Normalize(CVector3 vNormal)
{
	double Magnitude;							// This holds the magnitude			

	Magnitude = Mag(vNormal);					// Get the magnitude

	vNormal.x /= (float)Magnitude;				// Divide the vector's X by the magnitude
	vNormal.y /= (float)Magnitude;				// Divide the vector's Y by the magnitude
	vNormal.z /= (float)Magnitude;				// Divide the vector's Z by the magnitude

	return vNormal;								// Return the normal
}

//This function computes the normals and vertex normals of the objects
void ObjLoader::computeNormals(t3DModel *model)
{
	CVector3 vVector1, vVector2, vNormal, vPoly[3];

	// If there are no objects, we can skip this part
	if(model->numOfObjects <= 0)
		return;

	// Go through each of the objects to calculate their normals
	for(int index = 0; index < model->numOfObjects; index++)
	{
		// Get the current object
		t3DObject *object = &(model->object[index]);

		// Here we allocate all the memory we need to calculate the normals
		CVector3 *pNormals		= new CVector3 [object->numberOfFaces];
		CVector3 *pTempNormals	= new CVector3 [object->numberOfFaces];
		object->pNormals		= new CVector3 [object->numberOfVertices];

		// Go though all of the faces of this object
		for(int i=0; i < object->numberOfFaces; i++)
		{
			// To cut down LARGE code, we extract the 3 points of this face
			vPoly[0] = object->vertices[object->faces[i].vertIndex[0]];
			vPoly[1] = object->vertices[object->faces[i].vertIndex[1]];
			vPoly[2] = object->vertices[object->faces[i].vertIndex[2]];

			// Now let's calculate the face normals (Get 2 vectors and find the cross product of those 2)

			vVector1 = Vector(vPoly[0], vPoly[2]);		// Get the vector of the polygon (we just need 2 sides for the normal)
			vVector2 = Vector(vPoly[2], vPoly[1]);		// Get a second vector of the polygon

			vNormal  = Cross(vVector1, vVector2);		// Return the cross product of the 2 vectors (normalize vector, but not a unit vector)
			pTempNormals[i] = vNormal;					// Save the un-normalized normal for the vertex normals
			vNormal  = Normalize(vNormal);				// Normalize the cross product to give us the polygons normal

			pNormals[i] = vNormal;						// Assign the normal to the list of normals
		}

		//////////////// Now Get The Vertex Normals /////////////////

		CVector3 vSum = {0.0, 0.0, 0.0};
		CVector3 vZero = vSum;
		int shared=0;

		for (int i = 0; i < object->numberOfVertices; i++)			// Go through all of the vertices
		{
			for (int j = 0; j < object->numberOfFaces; j++)	// Go through all of the triangles
			{												// Check if the vertex is shared by another face
				if (object->faces[j].vertIndex[0] == i || 
					object->faces[j].vertIndex[1] == i || 
					object->faces[j].vertIndex[2] == i)
				{
					vSum = AddVector(vSum, pTempNormals[j]);// Add the un-normalized normal of the shared face
					shared++;								// Increase the number of shared triangles
				}
			}      
			
			CVector3 v;//
			v.x = 0;
			v.y = 0;
			v.z = 0;
			// Get the normal by dividing the sum by the shared.  We negate the shared so it has the normals pointing out.
			object->pNormals[i] = v;//DivideVectorByScaler(vSum, float(-shared));

			// Normalize the normal for the final vertex normal
			object->pNormals[i] = Normalize(object->pNormals[i]);	

			vSum = vZero;									// Reset the sum
			shared = 0;										// Reset the shared
		}
	
		// Free our memory and start over on the next object
		delete [] pTempNormals;
		delete [] pNormals;
	}
}

void ObjLoader::createTexture(UINT textureArray[], LPSTR fileName, int textureID)
{
	AUX_RGBImageRec *pBitmap = NULL;
	
	if(!fileName)									// Return from the function if no file name was passed in
		return;

	pBitmap = auxDIBImageLoad(fileName);				// Load the bitmap and store the data
	
	if(pBitmap == NULL)									// If we can't load the file, quit!
		exit(0);

	// Generate a texture with the associative texture ID stored in the array
	glGenTextures(1, &textureArray[textureID]);

	// This sets the alignment requirements for the start of each pixel row in memory.
	glPixelStorei (GL_UNPACK_ALIGNMENT, 1);

	// Bind the texture to the texture arrays index and init the texture
	glBindTexture(GL_TEXTURE_2D, textureArray[textureID]);

	// Build Mipmaps (builds different versions of the picture for distances - looks better)
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, pBitmap->sizeX, pBitmap->sizeY, GL_RGB, GL_UNSIGNED_BYTE, pBitmap->data);

	// Lastly, we need to tell OpenGL the quality of our texture map.  GL_LINEAR_MIPMAP_LINEAR
	// is the smoothest.  GL_LINEAR_MIPMAP_NEAREST is faster than GL_LINEAR_MIPMAP_LINEAR, 
	// but looks blochy and pixilated.  Good for slower computers though.  
		
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

	// Now we need to free the bitmap data that we loaded since openGL stored it as a texture

	if (pBitmap)										// If we loaded the bitmap
	{
		if (pBitmap->data)								// If there is texture data
		{
			free(pBitmap->data);						// Free the texture data, we don't need it anymore
		}

		free(pBitmap);									// Free the bitmap structure
	}
}

	// Since the .obj file format doesn't hold material information like color
	// and image files, we need to manually do that in code (or add it to the .obj file yourself).
	// I created a couple functions in the ObjLoader to help us do that.

	// Here we create a material with a name, a bitmap name and RGB values.
	// If we didn't want to create a texture map material but a color, just pass in NULL
	// for the file name field.  Also, by default the color is (255, 255, 255) if you choose
	// not to set it.  I did so just to show you how and not to confuse anyone.
//	g_LoadObj.addMaterial(&g_3DModel, "Bone", "sphere3_auv.bmp");
//	g_LoadObj.addMaterial(&g_3DModel, "Bone", "cylinder1_auv.bmp");

void t3DModel::draw(float a, float b, float c)
{
	glPushName(100);

	//g_Octree.DrawOctree(&g_Octree);

	glPushMatrix();
	
	for(int i = 0; i < numOfObjects; i++)
	{
		// Make sure we have valid objects just in case. (size() is in the vector class)
		if(object.size() <= 0) break;

		// Get the current object that we are displaying
		t3DObject *object = &this->object[i];

		// Check to see if this object has a texture map, if so bind the texture to it.
		if(object->hasTexture)
		{
			// Turn on texture mapping and turn off color
			glEnable(GL_TEXTURE_2D);

			// Reset the color to normal again
			glColor3ub(255, 255, 255);

			// Bind the texture map to the object by it's materialID (*ID Current Unused*)
			glBindTexture(GL_TEXTURE_2D, g_Texture[object->materialID]);
		}
		else
		{
			// Turn off texture mapping and turn on color
			glDisable(GL_TEXTURE_2D);

			// Reset the color to normal again
			glColor3ub(255, 255, 255);
		}

		// This determines if we are in wire frame or normal mode
		glBegin(GL_TRIANGLES);					// Begin drawing with our selected mode (triangles or lines)

			// Go through all of the faces (polygons) of the object and draw them
			for(int j = 0; j < object->numberOfFaces; j++)
			{
				// Go through each corner of the triangle and draw it.
				for(int whichVertex = 0; whichVertex < 3; whichVertex++)
				{
					// Get the vertex index for each point of the face
					int vertIndex = object->faces[j].vertIndex[whichVertex];

					//if (!g_Frustum.pointInFrustum(object->vertices[vertIndex].x, object->vertices[vertIndex].y, object->vertices[vertIndex].z))
					//	return;

					// Give OpenGL the normal for this vertex.
					glNormal3f(object->pNormals[ vertIndex ].x, object->pNormals[ vertIndex ].y, object->pNormals[ vertIndex ].z);
				
					// If the object has a texture associated with it, give it a texture coordinate.
					if(object->hasTexture) {

						// Make sure there was a UVW map applied to the object or else it won't have tex coords.
						if(object->textureVertices) {

							// Get the texture coordinate index
							int coordIndex = object->faces[j].coordIndex[whichVertex];

							// Assign the UV coordinates to the current vertex being rendered
							glTexCoord2f(object->textureVertices[ coordIndex ].x, object->textureVertices[ coordIndex ].y);
						}
					}
					else
					{
							glColor3ub(object->r, object->g, object->b);
					}
					// Pass in the current vertex of the object (Corner of current face)
					glVertex3f(object->vertices[vertIndex].x, object->vertices[vertIndex].y, object->vertices[vertIndex].z);
					
				}
			}

		glEnd();								// End the drawing
		glDisable(GL_TEXTURE_2D);
		glColor3ub(255, 255, 255);
	}

	glPopMatrix();

	glPopName();
}

void t3DModel::setColor(int _r, int _g, int _b)
{
	for(int i = 0; i < numOfObjects; i++)
	{
		if(object.size() <= 0) break;
		object[i].setColor(_r, _g, _b);
	}
}

void t3DObject::setColor(int _r, int _g, int _b)
{
	r = _r;
	g = _g;
	b = _b;
}

/*t3DObject::t3DObject()
{
	setColor(255, 255, 255);
}*/